﻿using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Protocol.Transport
{
    public class TcpSocketChannel : SugarBase, IDisposable
    {
        private readonly Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        private readonly ManualResetEvent _operationDoneEvent = new ManualResetEvent(false);

        public int TimeoutMilliseconds { get; set; }

        public int MaxBufferSize { get; set; }

        public TcpSocketChannel()
        {
            TimeoutMilliseconds = 5000;
            MaxBufferSize = 2048;
        }

        public SocketError Connect(string hostName, int portNumber)
        {
            var result = SocketError.Success;
            var hostEntry = new DnsEndPoint(hostName, portNumber);
            var socketEventArg = new SocketAsyncEventArgs {RemoteEndPoint = hostEntry};
            socketEventArg.Completed += (s, e) =>
            {
                result = e.SocketError;
                _operationDoneEvent.Set();
            };

            _operationDoneEvent.Reset();
            _socket.ConnectAsync(socketEventArg);
            _operationDoneEvent.WaitOne(TimeoutMilliseconds);
            return result;
        }

        protected SocketError Send(byte[] data)
        {
            var response = SocketError.TimedOut;
            var socketEventArg = new SocketAsyncEventArgs
            {
                RemoteEndPoint = _socket.RemoteEndPoint,
                UserToken = null
            };

            socketEventArg.Completed += (s, e) =>
            {
                response = e.SocketError;
                _operationDoneEvent.Set();
            };

            socketEventArg.SetBuffer(data, 0, data.Length);
            _operationDoneEvent.Reset();
            _socket.SendAsync(socketEventArg);
            _operationDoneEvent.WaitOne(TimeoutMilliseconds);

            return response;
        }

        protected SocketError Receive(out byte[] data)
        {
            byte[] buffer = null;
            var response = SocketError.TimedOut;
            var socketEventArg = new SocketAsyncEventArgs {RemoteEndPoint = _socket.RemoteEndPoint};
            socketEventArg.SetBuffer(new Byte[MaxBufferSize], 0, MaxBufferSize);
            socketEventArg.Completed += (s, e) =>
            {
                if (e.SocketError == SocketError.Success)
                    buffer = e.Buffer.Skip(e.Offset).Take(e.BytesTransferred).ToArray();

                response = e.SocketError;
                _operationDoneEvent.Set();
            };

            _operationDoneEvent.Reset();
            _socket.ReceiveAsync(socketEventArg);
            _operationDoneEvent.WaitOne(TimeoutMilliseconds);
            data = buffer;
            return response;
        }

        public void Dispose()
        {
            _socket.Close();
        }
    }
}
